package com.googlecode.gwtbb.client.only.widget.basic;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.*;
import com.googlecode.gwtbb.client.only.GwtUtils;
import com.googlecode.gwtbb.client.shared.GwtbbSmess;
import com.googlecode.gwtbb.client.shared.remote.ClientResponse;
import com.googlecode.gwtbb.client.shared.util.StringUtils;

/**
 * @author Abraham Grief
 * @version $Id$
 * @since Apr 30, 2008
 */
public abstract class AsyncFileUploadBase extends Composite implements SourcesAsyncFileUploadEvents {
	public static final int STATUS_EMPTY = 1;
	public static final int STATUS_UPLOADING = 2;
	public static final int STATUS_ERROR = 3;
	public static final int STATUS_UPLOADED = 4;
	public static final int STATUS_DELETED = 5;

	protected final ClickListener promptFileUploadClickListener = new ClickListener() {
		public void onClick(Widget sender) {
			promptFileUpload();
		}
	};

	// contains all of the child widgets
	private FlowPanel entryPanel;
	// contains the single displayable widget
	private SimplePanel displayPanel;


	// the hidden form that contains the uploading inputs
	private FormPanel formPanel;
	// the actual file upload!
	private FileUpload fileUpload;
	// the url the uploads are submitted to
	private String formAction = GWT.getModuleBaseURL() + "../../gwtbbUploader/picture";

	// used for display
	private String clientFileName;
	// used by containing form to tell the server what file to use
	private String serverFileName;
	// if there is a problem with the upload, the error message will be here
	private String errorMsg;
	// current state of the widget
	private int status = STATUS_EMPTY;
	// keep track of the submit attempts so that if the user changes which file to upload,
	// and the responses are returned out of order, we'll know which to use
	private int submitAttempt;
	// listeners used to provide custom functionality
	private AsyncFileUploadListenerCollection asyncFileUploadListeners;

	// Sometimes the result of the upload contains additional information, such as the case with pictures.
	// Such information is stored here.
	private String additionalInfo;

	public AsyncFileUploadBase(String formAction) {
		this.formAction = formAction;
		asyncFileUploadListeners = new AsyncFileUploadListenerCollection();
		entryPanel = new FlowPanel();
		initWidget(entryPanel);
		displayPanel = new SimplePanel();
		entryPanel.add(displayPanel);
		displayPanel.setWidget(getStatusEmptyWidget());
	}

	public String getAdditionalInfo() {
		return additionalInfo;
	}

	public String getClientFileName() {
		return clientFileName;
	}

	public String getErrorMsg() {
		return errorMsg;
	}

	public FileUpload getFileUpload() {
		if (fileUpload == null) {
			formPanel = new FormPanel();
			formPanel.setVisible(false); // don't display the formPanel unless we need to... it's ugly
			entryPanel.add(formPanel);
			formPanel.setAction(formAction);
			formPanel.setEncoding(FormPanel.ENCODING_MULTIPART);
			formPanel.setMethod(FormPanel.METHOD_POST);
			final VerticalPanel formInputsPanel = new VerticalPanel();
			formPanel.setWidget(formInputsPanel);
			formInputsPanel.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);
			final Hidden submitAttemptInput = new Hidden(ClientResponse.FILE_KEY, String.valueOf(submitAttempt));
			formInputsPanel.add(submitAttemptInput);
			fileUpload = new FileUpload();
			fileUpload.setName(ClientResponse.FILE_UPLOAD);
			formInputsPanel.add(fileUpload);
			Button submitButton = new Button("Upload file");
			submitButton.addClickListener(new ClickListener() {
				public void onClick(Widget sender) {
					submitFormPanel();
				}
			});
			formInputsPanel.add(submitButton);

			formPanel.addFormHandler(new FormHandler() {
				public void onSubmit(FormSubmitEvent event) {
					if (fireOnSubmit())
						event.setCancelled(true);
					else {
						String fileName = fileUpload.getFilename();
						AsyncFileUploadValidateFileNameEvent validateEvent = fireValidateFileName(fileName);
						if (validateEvent.isInvalid()) {
							event.setCancelled(true);
							setStatus(STATUS_ERROR, null, validateEvent.getErrorMsg(), null);
							// uploadWidget is already being displayed
						} else {
							submitAttemptInput.setValue(String.valueOf(++submitAttempt));
							setStatus(STATUS_UPLOADING, null, null, null);
							displayPanel.setWidget(getStatusUploadingWidget());
							onUploading(fileName);
						}
					}
				}

				public void onSubmitComplete(FormSubmitCompleteEvent event) {
					fireOnSubmitComplete(event.getResults());
					boolean serverError = true; // if we received an invalid communication from server
					try {
						String result = event.getResults();
						if (result != null) {
							String prefix = "<PRE>";
							String suffix = "</PRE>";
							if (result.startsWith(prefix))
								result = result.substring(prefix.length());
							if (result.endsWith(suffix))
								result = result.substring(0, result.length() - suffix.length());
							prefix = "<pre>";
							suffix = "</pre>";
							if (result.startsWith(prefix))
								result = result.substring(prefix.length());
							if (result.endsWith(suffix))
								result = result.substring(0, result.length() - suffix.length());

							String[] resp = result.split(ClientResponse.TEXT_DELIM);

							String responseCode = resp[0];
							if (ClientResponse.OK.equals(responseCode)) {
								int responseAttempt = Integer.parseInt(resp[1]);
								if (responseAttempt == submitAttempt) {
									setStatus(STATUS_UPLOADED, resp[2], null, resp[3]);
									serverError = false;
								} else {
									// response of previous submitAttempt... ignore
									serverError = false;
								}
							} else if (ClientResponse.ERROR.equals(responseCode)) {
								String serverErrorMsg = GwtbbSmess.instance().asyncFileUploadClientError(resp[1]);
								setStatus(STATUS_ERROR, null, serverErrorMsg, null);
								fireOnError(serverErrorMsg);
								serverError = false; // an error, but a vaild communication
							}
						}
					} catch (Exception e) {
						// ignore arrayIndexOutOfBounds and NumberFormatExceptions... they're server errors
					}
					if (serverError) {
						String serverFailureMsg = GwtbbSmess.instance().asyncFileUploadServerError();
						setStatus(STATUS_ERROR, null, serverFailureMsg, null);
						fireOnError(serverFailureMsg);
					}
					if (status == STATUS_UPLOADED) {
						displayPanel.setWidget(getStatusUploadedWidget());
						onUploaded();
					} else if (status == STATUS_ERROR) {
						displayPanel.setWidget(getStatusErrorWidget());
					}
				}
			});
		}
		return fileUpload;
	}

	public String getServerFileName() {
		return serverFileName;
	}

	public int getStatus() {
		return status;
	}

	public void addAsyncFileUploadEventListener(AsyncFileUploadListener listener) {
		asyncFileUploadListeners.add(listener);
	}

	public abstract Widget getStatusDeletedWidget();

	public abstract Widget getStatusEmptyWidget();

	public Widget getStatusErrorWidget() {
		return getStatusEmptyWidget();
	}

	public abstract Widget getStatusUploadedWidget();

	public abstract Widget getStatusUploadingWidget();

	public boolean isEmpty() {
		return status == STATUS_EMPTY;
	}

	public boolean isUploaded() {
		return status == STATUS_UPLOADED;
	}

	public boolean isUploading() {
		return status == STATUS_UPLOADING;
	}

	public abstract void onDeleted();

	public abstract void onUploaded();

	public abstract void onUploading(String fileName);

	public void promptFileUpload() {
		FileUpload fileUpload = getFileUpload();
		String userAgent = GwtUtils.getUserAgent();
		if (userAgent.toLowerCase().contains("msie")) {
			GwtUtils.click(fileUpload);
			submitFormPanel();
		} else
			formPanel.setVisible(true); // getFileUpload will init formPanel
	}

	public void removeAsyncFileUploadEventListener(AsyncFileUploadListener listener) {
		asyncFileUploadListeners.remove(listener);
	}

	/**
	 * Makes the widget ready to upload a different file.
	 * Any uploads that were started previously will now be ignored.
	 */
	public void reset() {
		setStatus(STATUS_EMPTY, null, null, null);
		clientFileName = null;
		displayPanel.setWidget(getStatusEmptyWidget());
	}

	/**
	 * Marks the upload as not to be used by the server.  Does not delete any files anywhere.
	 */
	protected void deleteUpload() {
		setStatus(STATUS_DELETED, serverFileName, errorMsg, additionalInfo);
		displayPanel.setWidget(getStatusDeletedWidget());
		onDeleted();
	}

	/**
	 * Puts this fileUpload in a state such that it appears that it uploaded an already existing file on the server.
	 *
	 * @param serverFileName the serverFileName of the existing file
	 * @param additionalInfo if applicable
	 */
	protected void fakeUpload(String serverFileName, String additionalInfo) {
		setStatus(STATUS_UPLOADED, serverFileName, null, additionalInfo);
		displayPanel.setWidget(getStatusUploadedWidget());
		onUploaded();
	}

	protected String getShortClientFileName() {
		String clientFileName = getClientFileName();
		// clientFileName can be null if we inited from a file from the server
		if (StringUtils.isEmpty(clientFileName))
			clientFileName = getServerFileName();
		if (StringUtils.isNotEmpty(clientFileName)) {
			int slashIndex = clientFileName.lastIndexOf('/'), slashIndex2 = clientFileName.lastIndexOf('\\');
			if (slashIndex2 > slashIndex)
				slashIndex = slashIndex2;
			if (slashIndex >= 0 && slashIndex < clientFileName.length() - 1)
				clientFileName = clientFileName.substring(slashIndex + 1);
		}
		return clientFileName;
	}

	private void fireOnError(String errorInfo) {
		asyncFileUploadListeners.fireOnError(this, errorInfo);
	}

	private boolean fireOnSubmit() {
		return asyncFileUploadListeners.fireOnSubmit(formPanel);
	}

	private void fireOnSubmitComplete(String results) {
		asyncFileUploadListeners.fireOnComplete(formPanel, results);
	}

	private AsyncFileUploadValidateFileNameEvent fireValidateFileName(String fileName) {
		return asyncFileUploadListeners.fireOnValidateFileName(this, fileName);
	}

	private void setStatus(int status, String serverFileName, String errorMsg, String additionalInfo) {
		this.status = status;
		this.serverFileName = serverFileName;
		this.errorMsg = errorMsg;
		this.additionalInfo = additionalInfo;
	}

	private void submitFormPanel() {
		if (StringUtils.isNotEmpty(clientFileName = fileUpload.getFilename())) {
			setStatus(STATUS_EMPTY, null, null, null);
			formPanel.submit();
			formPanel.setVisible(false);
		}
	}
}